home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 7
/
Aminet 7 - August 1995.iso
/
Aminet
/
comm
/
net
/
sana2_cslip.lha
/
cslip.c
< prev
next >
Wrap
C/C++ Source or Header
|
1993-06-29
|
19KB
|
664 lines
/*
* COMPRESSED TCP HEADERS
*
* Reduce the 40 byte TCP header to as little as 5 or even 3 bytes
* (of which 2 bytes are the original TCP checksum).
*
* See RFC 1144 "Compression of TCP/IP Headers for Low-Speed Serial
* Links", V. Jacobson, February 1990, for the exciting details.
*
* The code in this file is derived from and very similar to the example
* implementation provided in RFC 1144, and is therefore sort-of covered
* by its copyright:
*
* Copyright (C) 1989 Regents of the University of California.
*
* This adaptation by Olaf 'Rhialto' Seibert.
* - fixed a number of sizeof(int) == 4 assumptions.
* - allow a few packets to be sent compressed before it is officially
* enabled, so as to trigger the other side into compressed mode.
*
* Memory usage: < 3K of 68000 code, plus ~ 4K buffer space per line.
*/
#include <string.h>
#include "slip_device.h"
#if DEBUG
#include "syslog.h"
#else
#define debug(x)
#endif
#define calloc(n,m) AllocVec((n) * (m), MEMF_CLEAR)
#define free(p) FreeVec(p)
#ifndef EXEC_MEMORY_H
#include <exec/memory.h>
#endif
#include <clib/exec_protos.h>
#ifdef __SASC
#include <pragmas/exec_pragmas.h>
#endif
#define ihl(v_ihl) lonibble(v_ihl) /* extract ip header length */
#define tcp_offset(tcp) ((u_char)(tcp)->offset >> DSHIFT)
/* Appendix A.2 Compression */
u_char
sl_compress_tcp(m, comp)
struct mbuf *m;
struct slcompress *comp;
{
register struct cstate *cs = comp->last_cs->cs_next;
register struct ip_header *ip = mtod(m, struct ip_header *);
register u_int hlen = ihl(ip->v_ihl);
register struct tcp_header *oth; /* last TCP header */
register struct tcp_header *th; /* current TCP header */
register u_long deltaS, deltaA; /* general purpose temporaries */
register u_int changes = 0; /* change mask */
u_char new_seq[16]; /* changes from last to current */
register u_char *cp = new_seq;
debug((">sl_compress_tcp %lx %d\n", m->m_off, m->m_len));
/* Appendix B.2 Backwards compatible SLIP servers */
#ifdef TRYCOUNT
if (comp->on == 0) {
debug(("<sl_compress_tcp: not on\n"));
return TYPE_IP;
}
#else
if ((comp->flags & SLF_ON) == 0) {
debug(("<sl_compress_tcp: not on\n"));
return TYPE_IP;
}
#endif
if (ip->protocol != TCP_PTCL) { /* Rhialto */
comp->sls_o_nontcp++;
debug(("<sl_compress_tcp: not tcp\n"));
return TYPE_IP;
}
/*
* Bail if this is an IP fragment or if the TCP packet isn't
* `compressible' (i.e., ACK isn't set or some other control bit is
* set). (We assume that the caller has already made sure the packet
* is IP proto TCP).
*/
if ((ip->fl_offs & htons(0x3fff)) || m->m_len < 40) {
comp->sls_o_tcp++;
debug(("<sl_compress_tcp: tcp frag\n"));
return TYPE_IP;
}
th = (struct tcp_header *)&((int32 *) ip)[hlen];
if ((th->flags & (SYN | FIN | RST | ACK)) != ACK) {
comp->sls_o_tcp++;
debug(("<sl_compress_tcp: tcp flags\n"));
return TYPE_IP;
}
/*
* Packet is compressible -- we're going to send either a
* COMPRESSED_TCP or UNCOMPRESSED_TCP packet. Either way we need to
* locate (or create) the connection state. Special case the most
* recently used connection since it's most likely to be used again &
* we don't have to do any reordering if it's used.
*/
if (ip->source != cs->cs_ip.source ||
ip->dest != cs->cs_ip.dest ||
*(int32 *)th != cs->cs_hdr4[ihl(cs->cs_ip.v_ihl)]) {
/*
* Wasn't the first -- search for it.
*
* States are kept in a circularly linked list with last_cs
* pointing to the end of the list. The list is kept in lru
* order by moving a state to the head of the list whenever
* it is referenced. Since the list is short and,
* empirically, the connection we want is almost always near
* the front, we locate states via linear search. If we don't
* find a state for the datagram, the oldest state is re-used.
*/
register struct cstate *lcs;
register struct cstate *lastcs = comp->last_cs;
do {
lcs = cs;
cs = cs->cs_next;
comp->sls_o_searches++;
if (ip->source == cs->cs_ip.source &&
ip->dest == cs->cs_ip.dest &&
*(int32 *)th == cs->cs_hdr4[ihl(cs->cs_ip.v_ihl)])
goto found;
} while (cs != lastcs);
/*
* Didn't find it -- re-use oldest cstate. Send an uncompressed
* packet that tells the other side what connection number
* we're using for this conversation. Note that since the
* state list is circular, the oldest state points to the newest
* and we only need to set last_cs to update the lru linkage.
*/
comp->sls_o_misses++;
comp->last_cs = lcs;
hlen += tcp_offset(th); /* add tcp header length */
hlen *= sizeof(int32);
goto uncompressed;
found:
/* Found it -- move to the front on the connection list. */
if (lastcs == cs)
comp->last_cs = lcs;
else {
lcs->cs_next = cs->cs_next;
cs->cs_next = lastcs->cs_next;
lastcs->cs_next = cs;
}
}
debug((" sl_compress_tcp: found\n"));
#ifdef TRYCOUNT
/*
* For found connections, try a few TCP_UNCOMPRESSED packets even if
* we're not SL_ON yet. (This doesn't happen on new connections;
* they will be TCP_UNCOMPRESSED regardless - FIXME.)
*/
if (comp->on > 0) {
comp->on--;
debug((" sl_compress_tcp: trycount %d\n", comp->on));
goto uncompressed;
}
#endif
/*
* Make sure that only what we expect to change changed. The 1st
* line of the 'if' checks the IP protocol version, header length &
* type of service. The 2nd line checks the "Don't fragment" bit.
* The 3rd line checks the time-to-live and protocol (the protocol
* check is unnecessary but costless). The 4th line checks the TCP
* header length. The 5th line checks IP options, if any. The 6th
* line checks TCP options, if any. If any of these things are
* different between the previous & current datagram, we send the
* current datagram "uncompressed".
*/
oth = (struct tcp_header *) &cs->cs_hdr4[hlen];
deltaS = hlen;
hlen += tcp_offset(th);
hlen *= sizeof(int32);
if (((u_short *) ip)[0] != ((u_short *) &cs->cs_ip)[0] || /* v_ihl,tos */
((u_short *) ip)[3] != ((u_short *) &cs->cs_ip)[3] || /* fl_offs */
((u_short *) ip)[4] != ((u_short *) &cs->cs_ip)[4] || /* ttl, protocol */
th->offset != oth->offset ||
(deltaS > 5 && BCMP(ip + 1, &cs->cs_ip + 1, (deltaS - 5) * sizeof(int32))) ||
(tcp_offset(th) > 5 && BCMP(th + 1, oth + 1, (tcp_offset(th) - 5) * sizeof(int32)))) {
debug((" sl_compress_tcp: header changed\n"));
goto uncompressed;
}
/*
* Figure out which of the changing fields changed. The receiver
* expects changes in the order: urgent, window, ack, seq.
*/
if (th->flags & URG) {
deltaS = ntohs(th->up);
ENCODEZ(deltaS);
changes |= NEW_U;
} else if (th->up != oth->up) {
/*
* argh! URG not set but urp changed -- a sensible
* implementation should never do this but RFC 793 doesn't
* prohibit the change so we have to deal with it.
*/
debug((" sl_compress_tcp: up changed\n"));
goto uncompressed;
}
if (deltaS = (u_short) (ntohs(th->wnd) - ntohs(oth->wnd))) {
ENCODE(deltaS);
changes |= NEW_W;
}
if (deltaA = ntohs(th->ack) - ntohs(oth->ack)) {
if (deltaA > 0xffff)
goto uncompressed;
ENCODE(deltaA);
changes |= NEW_A;
}
if (deltaS = ntohs(th->seq) - ntohs(oth->seq)) {
if (deltaS > 0xffff)
goto uncompressed;
ENCODE(deltaS);
changes |= NEW_S;
}
/*
* Look for the special-case encodings.
*/
switch (changes) {
case 0:
/*
* Nothing changed. If this packet contains data and the last
* one didn't, this is probably a data packet following an
* ack (normal on an interactive connection) and we send it
* compressed. Otherwise it's probably a retransmit,
* retransmitted ack or window probe. Send it uncompressed
* in case the other side missed the compressed version.
*/
if (ip->length != cs->cs_ip.length &&
ntohs(cs->cs_ip.length) == hlen)
break;
debug((" sl_compress_tcp: retransmission ->\n"));
/* fall through */
case SPECIAL_I:
case SPECIAL_D:
/*
* Actual changes match one of our special case encodings --
* send the packet uncompressed.
*/
debug((" sl_compress_tcp: changes match special\n"));
goto uncompressed;
case NEW_S | NEW_A:
if (deltaS == deltaA &&
deltaS == ntohs(cs->cs_ip.length) - hlen) {
/* special case for echoed terminal traffic */
debug((" sl_compress_tcp: special interactive\n"));
changes = SPECIAL_I;
cp = new_seq;
}
break;
case NEW_S:
if (deltaS == ntohs(cs->cs_ip.length) - hlen) {
/* special case for data xfer */
debug((" sl_compress_tcp: special data\n"));
changes = SPECIAL_D;
cp = new_seq;
}
break;
}
deltaS = ntohs(ip->id) - ntohs(cs->cs_ip.id);
if (deltaS != 1) {
ENCODEZ(deltaS);
changes |= NEW_I;
}
if (th->flags & PSH)
changes |= TCP_PUSH_BIT;
/*
* Grab the cksum before we overwrite it below. Then update our
* state with this packet's header.
*/
deltaA = ntohs(th->checksum);
BCOPY(ip, &cs->cs_ip, hlen);
/*
* We want to use the original packet as our compressed packet.
* (cp - new_seq) is the number of bytes we need for compressed
* sequence numbers. In addition we need 1 byte for the change
* mask, 1 for the connection id and 2 for the tcp checksum.
* So, (cp - new_seq) + 4 bytes of header are needed.
* hlen is how many bytes of the original packet we tossed so
* subtract the two to to get the packet size difference.
*/
deltaS = cp - new_seq;
cp = (u_char *)ip;
if (comp->last_xmit != cs->cs_id) {
comp->last_xmit = cs->cs_id;
hlen -= deltaS + 4;
cp += hlen;
*cp++ = changes | NEW_C;
*cp++ = cs->cs_id;
} else {
hlen -= deltaS + 3;
cp += hlen;
*cp++ = changes;
}
m->m_len -= hlen;
m->m_off += hlen;
*cp++ = deltaA >> 8; /* checksum */
*cp++ = deltaA;
BCOPY(new_seq, cp, deltaS);
comp->sls_o_compressed++;
debug(("<sl_compress_tcp: compressed\n"));
return TYPE_COMPRESSED_TCP;
uncompressed:
debug((" sl_compress_tcp: uncompressed\n"));
/*
* Update connection state cs & send uncompressed packet
* ('uncompressed' means a regular ip/tcp packet but with the
* 'conversation id' we hope to use on future compressed packets
* in the protocol field).
*/
BCOPY(m->m_off, cs->cs_hdr, hlen);
ip->protocol = cs->cs_id;
comp->last_xmit = cs->cs_id;
comp->sls_o_uncompressed++;
debug(("<sl_compress_tcp: uncompressed\n"));
return TYPE_UNCOMPRESSED_TCP;
}
/* OIS:
* This function should be called if the most recently compressed packet
* may have been lost on its way to the next gateway. It forces the next
* packet on the same connection to be uncompressed, in an attempt to
* minimise the effect of the dropped packet. The method used is to change
* one of the 'unchanging' header fields in the saved header, so that none
* of the other saved headers will be dropped.
*/
void
sl_xmit_error(comp)
struct slcompress *comp;
{
if (comp->last_xmit < MAX_STATES) {
comp->tstate[comp->last_xmit].cs_ip.v_ihl--;
}
}
/* Appendix A.3 Decompression */
struct mbuf *
sl_uncompress_tcp(m, type, comp)
struct mbuf *m;
u_int type;
struct slcompress *comp;
{
register char *bufp;
register u_char *cp;
register u_int hlen, changes;
register struct tcp_header *th;
register struct cstate *cs;
register struct ip_header *ip;
int len;
debug((">sl_uncompress_tcp\n"));
switch (type) {
case TYPE_ERROR:
default:
debug((" sl_uncompress_tcp: ERROR\n"));
goto bad;
case TYPE_IP:
debug((" sl_uncompress_tcp: IP\n"));
comp->sls_i_ip++;
#ifdef SLF_MAYBE
/*
* If we receive TYPE_IP packets, investigate if they could have
* been sent compressed. If so, the other side probably doesn't
* understand compressed packets. This test is identical to those
* in sl_compress_tcp().
* (OIS)
*/
bufp = m->m_off;
ip = (struct ip_header *)bufp;
if (comp->flags & SLF_MAYBE &&
ip->protocol == TCP_PTCL &&
(ip->fl_offs & htons(0x3fff)) == 0 &&
(th = (struct tcp_header *)
offset(m, ihl(ip->v_ihl) * sizeof(int32), sizeof(*th))) &&
(th->flags & (SYN | FIN | RST | ACK)) == ACK) {
comp->flags &= ~(SLF_ON | SLF_MAYBE) ;
/* printf("Compression turned OFF\n"); */
}
#endif
return m;
case TYPE_UNCOMPRESSED_TCP:
debug((" sl_uncompress_tcp: UNCOMPRESSED TCP\n"));
comp->sls_i_uncompressed++;
/* Appendix B.2 Backwards compatible SLIP servers */
if ((comp->flags & SLF_ALLOWED) == 0)
goto bad;
#ifdef TRYCOUNT
comp->on = SL_ON;
#else
comp->flags |= SLF_ON;
#endif
/*
* Locate the saved state for this connection. If the state index
* is legal, clear the 'discard' flag.
*/
bufp = m->m_off;
ip = (struct ip_header *)bufp;
if (ip->protocol > comp->rslot_limit)
goto bad;
cs = &comp->rstate[comp->last_recv = ip->protocol];
comp->flags &= ~SLF_TOSS;
/*
* Restore the IP protocol field then save a copy of this
* packet header. (The checksum is zeroed in the copy so we
* don't have to zero it each time we process a compressed
* packet.)
*/
ip->protocol = TCP_PTCL;
hlen = ihl(ip->v_ihl);
hlen += tcp_offset((struct tcp_header *) & ((int32 *)ip)[hlen]);
hlen *= sizeof(int32);
BCOPY(ip, cs->cs_hdr, hlen);
cs->cs_ip.checksum = 0;
cs->cs_hlen = hlen;
return m;
case TYPE_COMPRESSED_TCP:
debug((" sl_uncompress_tcp: COMPRESSED TCP\n"));
comp->sls_i_compressed++;
/* Appendix B.2 Backwards compatible SLIP servers */
if ((comp->flags & SLF_ALLOWED) == 0)
goto bad;
break;
}
/* We've got a compressed packet. */
bufp = m->m_off;
cp = bufp;
changes = *cp++;
if (changes & NEW_C) {
/*
* Make sure the state index is in range, then grab the
* state. If we have a good state index, clear the 'discard'
* flag.
*/
if (*cp > comp->rslot_limit)
goto bad;
comp->flags &= ~SLF_TOSS;
comp->last_recv = *cp++;
} else {
/*
* This packet has an implicit state index. If we've had a
* line error since the last time we got an explicit state
* index, we have to toss the packet.
*/
if (comp->flags & SLF_TOSS) {
comp->sls_i_tossed++;
debug(("<sl_uncompress_tcp: tossed\n"));
return NULL;
}
}
/*
* Find the state then fill in the TCP checksum and PUSH bit.
*/
cs = &comp->rstate[comp->last_recv];
hlen = ihl(cs->cs_ip.v_ihl) * sizeof(int32);
th = (struct tcp_header *) &((u_char *) &cs->cs_ip)[hlen];
th->checksum = htons((*cp << 8) | cp[1]);
cp += 2;
if (changes & TCP_PUSH_BIT)
th->flags |= PSH;
else
th->flags &= ~PSH;
/*
* Fix up the state's ack, seq, urg and win fields based on the
* changemask.
*/
switch (changes & SPECIALS_MASK) {
case SPECIAL_I:
{
register u_int i = ntohs(cs->cs_ip.length) - cs->cs_hlen;
debug((" sl_uncompress_tcp: special interactive\n"));
th->ack = htonl(ntohl(th->ack) + i);
th->seq = htonl(ntohl(th->seq) + i);
}
break;
case SPECIAL_D:
debug((" sl_uncompress_tcp: special data\n"));
th->seq = htonl(ntohl(th->seq) + ntohs(cs->cs_ip.length)
- cs->cs_hlen);
break;
default:
debug((" sl_uncompress_tcp: other changemask\n"));
if (changes & NEW_U) {
th->flags |= URG;
DECODEU(th->up);
} else
th->flags &= ~URG;
if (changes & NEW_W)
DECODES(th->wnd);
if (changes & NEW_A)
DECODEL(th->ack);
if (changes & NEW_S)
DECODEL(th->seq);
}
/* Update the new IP ID */
if (changes & NEW_I)
DECODES(cs->cs_ip.id)
else
cs->cs_ip.id = htons(ntohs(cs->cs_ip.id) + 1);
/*
* At this point we have done the whole header.
* cp points to the first byte of data in the packet. We skip the
* 4-byte alignment here as SANA will copy it to the user anyway.
* Back up cp by the TCP/IP header length to make room for the
* reconstructed header (we assume the packet we were handed has
* enough space to prepend 128 bytes of header). Adjust the length
* to account for the new header & fill in the IP total length.
*
*/
len = m->m_len;
len -= (cp - bufp);
if (len < 0)
/*
* We must have dropped some characters (crc should detect this
* but the slip framing won't)
*/
goto bad;
#if 0
if ((int32)cp & 3) {
if (len > 0)
OVBCOPY(cp, (int32)cp & ~3, len);
cp = (u_char *) ((int32)cp & ~3);
}
#endif
m->m_off = cp -= cs->cs_hlen;
m->m_len = len += cs->cs_hlen;
cs->cs_ip.length = htons(len);
debug((" sl_uncompress_tcp: copy long header into packet\n"));
BCOPY(&cs->cs_ip, cp, cs->cs_hlen);
/* recompute the ip header checksum */
{
/*register u_short *bp = (u_short *)cp;*/
register u_short *bp = (u_short *)&cs->cs_ip;
for (changes = 0; hlen > 0; hlen -= 2)
changes += ntohs(*bp++);
changes = (changes & 0xffff) + (changes >> 16);
changes = (changes & 0xffff) + (changes >> 16);
changes = htons(~changes);
bp = &((struct ip_header *)cp)->checksum;
((u_char *)bp)[0] = changes >> 8;
((u_char *)bp)[1] = changes;
}
debug(("<sl_uncompress_tcp: done\n"));
return m;
bad:
comp->sls_i_error++;
comp->flags |= SLF_TOSS;
debug(("<sl_uncompress_tcp: bad packet\n"));
return NULL;
}
/* Appendix A.4 Initialisation */
void
sl_compress_init(comp, rslots, tslots)
struct slcompress *comp;
int rslots;
int tslots;
{
register u_int i;
register struct cstate *tstate = NULL;
/*
* Clean out any junk left from the last time the line was used.
*/
memset(comp, 0, sizeof(*comp));
if ( rslots > 0 && rslots < 256 ) {
comp->rstate = calloc( rslots, sizeof(struct cstate) );
comp->rslot_limit = rslots - 1;
}
if ( tslots > 0 && tslots < 256 ) {
tstate = calloc( tslots, sizeof(struct cstate) );
comp->tstate = tstate;
comp->tslot_limit = tslots - 1;
}
/*
* Link the transmit states into a circular list.
*/
if (tstate > 0) {
for (i = comp->tslot_limit; i > 0; --i) {
tstate[i].cs_id = i;
tstate[i].cs_next = &tstate[i - 1];
}
tstate[0].cs_id = 0;
tstate[0].cs_next = &tstate[comp->tslot_limit];
comp->last_cs = &tstate[0];
}
/*
* Make sure we don't accidentally do CID compression
* (assumes MAX_STATES < 255).
*/
comp->last_recv = NO_CID;
comp->last_xmit = NO_CID;
/*
* Set proper flags
*/
comp->flags = SLF_TOSS | SLF_ALLOWED;
#ifdef TRYCOUNT
if (tslots > 0)
comp->on = TRYCOUNT;
#else
if (tslots > 0)
comp->flags |= SLF_ON;
#endif
}
void
sl_compress_deinit(comp)
struct slcompress *comp;
{
if (comp->rstate)
free(comp->rstate);
if (comp->tstate)
free(comp->tstate);
}